package com.study.crypto.certificate.server.advice;

import com.alibaba.fastjson.JSON;
import com.study.crypto.certificate.server.exception.SignatureException;
import com.study.crypto.dto.SignInfoDto;
import com.study.crypto.dto.ca.base.ResponseCaDto;
import com.study.crypto.general.spring.annotation.RequireVerify;
import com.study.crypto.signer.Signer;
import com.study.crypto.signer.SignerFactory;
import com.study.crypto.utils.ResourceUtils;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.x509.Certificate;
import org.bouncycastle.util.encoders.Base64;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;

import java.io.IOException;
import java.lang.reflect.Type;
import java.security.GeneralSecurityException;

/**
 * 请求参数进行验签通知，参数使用了 @RequireVerify 注解
 * @author Songjin
 * @since 2022-11-01 18:59
 */
@Slf4j
@ControllerAdvice
public class RequestBodyVerifyAdvice extends RequestBodyAdviceAdapter implements InitializingBean {

    private Signer signer;
    /** 客户端证书，用于接口签名验证 */
    private Certificate clientCert;

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return methodParameter.hasParameterAnnotation(RequireVerify.class);
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        log.info("参数需要进行验签---->");
        String signInfoProperty = "signInfo";
        BeanWrapperImpl beanWrapper = new BeanWrapperImpl(body);
        SignInfoDto signInfo = (SignInfoDto) beanWrapper.getPropertyValue(signInfoProperty);
        if (log.isInfoEnabled()) {
            log.info("参数验签，签名数据: {}", JSON.toJSONString(signInfo));
        }
        beanWrapper.setPropertyValue(signInfoProperty, null);
        String taskCode = (String) beanWrapper.getPropertyValue("taskCode");
        String version = (String) beanWrapper.getPropertyValue("version");
        ResponseCaDto entity = ResponseCaDto.builder().taskCode(taskCode).version(version).resultCode("-1").build();
        try {
            byte[] signature = Base64.decode(signInfo.getSignValue());
            boolean verify = signer.verify(JSON.toJSONBytes(body), signature, this.clientCert);
            if (verify) {
                beanWrapper.setPropertyValue(signInfoProperty, signInfo);
                return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
            } else {
                entity.setResultCodeMsg("验签失败");
                throw new SignatureException("验签失败", entity);
            }
        } catch (IOException | GeneralSecurityException e) {
            entity.setResultCodeMsg("验签异常");
            throw new SignatureException("验签异常", entity, e);
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        signer = SignerFactory.produce(GMObjectIdentifiers.sm2sign_with_sm3);
        // 客户端签名证书
        byte[] clientCertBytes = ResourceUtils.toByteArray("client-certs/sm2-client-cert.cer");
        clientCert = Certificate.getInstance(clientCertBytes);
    }
}
